﻿#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.Animations;


namespace AutoConstraintDressed
{
	public class ConstraintDressedUtils : EditorWindow
	{
		private Animator avatarAnimator, clothAnimator;
		private Dictionary<string, string[]> aliasBoneName;//储存骨骼别名
		private List<string> bonesName, bonesNameCopy;//骨骼名字列表
		public string aliasCloth = "";
		public bool isIgnoreCase = true;
		public bool isSubString;
		public bool isDeleteOldConstraint=false;

		protected void AutoConstraintDressed(GameObject sourceAvatar, GameObject sourceCloth)
		{
			bonesName = new List<string>(HumanTrait.BoneName);
			bonesName.Insert(21, "Armature");
			bonesName.RemoveRange(22, bonesName.Count - 22);
			bonesNameCopy = new List<string>(bonesName);
			aliasBoneName = new Dictionary<string, string[]>();
			SetAliasBoneName();

			avatarAnimator = sourceAvatar.GetComponent<Animator>();
			clothAnimator = sourceCloth.GetComponent<Animator>();

			if (avatarAnimator == null)
			{
				EditorUtility.DisplayDialog("错误", "模型没有Animator组件", "确认", "取消");
				return;
			}
			else if(avatarAnimator.avatar == null)
            {
				EditorUtility.DisplayDialog("错误", "模型Animator组件未设置avatar", "确认", "取消");
				return;
			}

			
			if (clothAnimator == null)
			{
				var flag = EditorUtility.DisplayDialog("警告", "衣服无Animator，将使用常用骨骼名称匹配，可能无法找到相应骨骼，是否继续？", "继续", "取消");
				if (!flag)
					return;
			}
			else if(clothAnimator.avatar == null)
            {
				var flag = EditorUtility.DisplayDialog("警告", "衣服Animator未设置Avatar，将使用常用骨骼名称匹配，可能无法找到相应骨骼，是否继续？", "继续", "取消");
				if (!flag)
					return;
			}else if(!clothAnimator.avatar.isHuman)
            {
				var flag = EditorUtility.DisplayDialog("警告", "衣服非人型avatar，无法获取骨骼，是否继续？", "继续", "取消");
				if (!flag)
					return;
			}



			if (avatarAnimator != null && clothAnimator != null)
				AnimatorConstraintBind();
			else
			{
				NameConstraintBind(sourceCloth.transform);
				string s = "";
				if (bonesNameCopy.Count > 0)
				{
					bonesNameCopy.ForEach(bone => s += bone + " ");

					Debug.LogWarning("以下骨骼未找到：" + s);
				}
			}
		}
		
		protected void RemoveAllComponent(GameObject obj)
        {
			bonesName = new List<string>(HumanTrait.BoneName);
			bonesName.Insert(21, "Armature");
			bonesName.RemoveRange(22, bonesName.Count - 22);
			bonesNameCopy = new List<string>(bonesName);
			aliasBoneName = new Dictionary<string, string[]>();
			SetAliasBoneName();
			RemoveComponent(obj.transform);
		}

		protected void RemoveComponent(Transform objTransform)
        {
			int len = objTransform.childCount;
			for (int i = 0; i < len; i++)
			{
				Transform child = objTransform.GetChild(i);
				RemoveComponent(child);

				string name = child.gameObject.name;
				foreach (var bone in bonesNameCopy)
				{
					string[] aliasBoneArr = aliasBoneName[bone];
					foreach (string alias in aliasBoneArr)
					{
						if (alias.Equals(name))
						{
							if (bone.Equals("Head"))
								continue;
							else
							{
								var pc = child.transform.GetComponent<ParentConstraint>();
								if (pc != null)
									Object.DestroyImmediate(pc);
							}
							bonesNameCopy.Remove(bone);
							goto LOOPOUT;
						}
					}
				}

			LOOPOUT:;

			}
		}


		/// <summary>
		/// 通过Animator获取骨骼
		/// </summary>
		protected void AnimatorConstraintBind()
		{
			for (int i = 0; i < 22; i++)
			{

				Transform avatarTransform, clothTransform;
				avatarTransform = FixGetBoneTransform(avatarAnimator, i);
				clothTransform = FixGetBoneTransform(clothAnimator, i);
				if (avatarTransform == null || clothTransform == null)
				{
					Debug.LogWarning("获取骨骼" + bonesName[i] + "异常");
					continue;
				}
				if (i == (int)HumanBodyBones.Head)
				{
					clothTransform.name = clothTransform.name +"(" +aliasCloth+")";
					clothTransform.SetParent(avatarTransform);
					continue;
				}
				
				ConstraintBind(avatarTransform, clothTransform);
			}
		}

		/// <summary>
		/// 通过名字绑定
		/// </summary>
		/// <param name="srcCloth">要绑定的衣服</param>
		protected void NameConstraintBind(Transform srcCloth)
		{
			int len = srcCloth.childCount; 
			for (int i = 0; i < len; i++)
			{
				Transform child = srcCloth.GetChild(i);
				NameConstraintBind(child);

				string name = child.gameObject.name;
				foreach (var bone in bonesNameCopy)
                {
					string[] aliasBoneArr = aliasBoneName[bone];
					foreach (string alias in aliasBoneArr)
                    {
						if (MatchStr(alias, name))
                        {
							if (bone.Equals("Head", System.StringComparison.OrdinalIgnoreCase))
							{
								child.name = child.name + "(" + aliasCloth + ")";
								child.SetParent(FixGetBoneTransform(avatarAnimator, bonesName.LastIndexOf(bone)));
							}
							else
								ConstraintBind(FixGetBoneTransform(avatarAnimator, bonesName.LastIndexOf(bone)), child);
							bonesNameCopy.Remove(bone);
							goto LOOPOUT;
						}
                    }
				}
			

				/*
				for (int j = 0; j < 22; j++)
				{
					string[] aliasBone = aliasBoneName[bonesName[j]];
					foreach (string bone in aliasBone)
					if (bone.Equals(name))
					{
						ConstraintBind(FixGetBoneTransform(avatarAnimator,j),child);
							bonesNameCopy.Remove(bonesName[j]);
						goto LOOPOUT;
					}
				}*/
			LOOPOUT:;

			}
		}

		/// <summary>
		/// 为物体创建Parent Constraint组件并激活
		/// </summary>
		/// <param name="sourceTransform">绑定物体</param>
		/// <param name="constraintTransform">要添加组件的物体</param>
		protected void ConstraintBind(Transform sourceTransform, Transform constraintTransform)
		{

			GameObject cltBoneObj = constraintTransform.gameObject;

			ParentConstraint pcCloth = cltBoneObj.GetComponent<ParentConstraint>();
			if (pcCloth == null)
			{
				cltBoneObj.AddComponent<ParentConstraint>();
				pcCloth = cltBoneObj.GetComponent<ParentConstraint>();
			}
			else if (isDeleteOldConstraint)
            {
                DestroyImmediate(pcCloth);
				cltBoneObj.AddComponent<ParentConstraint>();
				pcCloth = cltBoneObj.GetComponent<ParentConstraint>();
			}
			else//不删除原有约束组件则直接返回
				return;



			ConstraintSource constraintSource = new ConstraintSource
			{
				sourceTransform = sourceTransform,
				weight = 1
			};

			for (int j = pcCloth.sourceCount - 1; j >= 0; j--)
				pcCloth.RemoveSource(j);

			pcCloth.AddSource(constraintSource);



			//此段代码来源：https://answers.unity.com/questions/1588112/how-to-activate-parent-constraint-via-api-the-same.html
			//var positionDelta = constraintTransform.position - sourceTransform.position;
			Vector3 positionOffset = sourceTransform.InverseTransformPoint(constraintTransform.position);
			pcCloth.SetTranslationOffset(0, positionOffset);
			
			//pcCloth.SetTranslationOffset(0, positionDelta);
			//pcCloth.SetTranslationOffset(0, Quaternion.Inverse(constraintTransform.rotation) * positionDelta);
			
			var rotationDelta = Quaternion.Inverse(sourceTransform.rotation) * constraintTransform.transform.rotation;
			pcCloth.SetRotationOffset(0, rotationDelta.eulerAngles);


			pcCloth.locked = true;
			pcCloth.constraintActive = true;
		}

		/// <summary>
		/// 初始化骨骼别名
		/// </summary>
		protected void SetAliasBoneName()
		{
			aliasBoneName.Add(bonesName[21], new string[] { "Amatrue", "Armature" });
			aliasBoneName.Add(bonesName[(int)HumanBodyBones.Hips], new string[] 			{ "Hips" });
			aliasBoneName.Add(bonesName[(int)HumanBodyBones.Spine], new string[] 			{ "Spine" });
			aliasBoneName.Add(bonesName[(int)HumanBodyBones.Head], new string[] 			{ "Head" });
			aliasBoneName.Add(bonesName[(int)HumanBodyBones.Neck], new string[] 			{ "Neck" });
			aliasBoneName.Add(bonesName[(int)HumanBodyBones.Chest], new string[] 			{ "Chest" });
			aliasBoneName.Add(bonesName[(int)HumanBodyBones.RightUpperLeg], new string[] 	{ "RightUpperLeg", 	"UpperLeg_R",	"UpperLeg.R" });
			aliasBoneName.Add(bonesName[(int)HumanBodyBones.LeftUpperLeg], new string[] 	{ "LeftUpperLeg", 	"UpperLeg_L",	"UpperLeg.L" });
			
			aliasBoneName.Add(bonesName[(int)HumanBodyBones.LeftLowerLeg], new string[] 	{ "LeftLowerLeg", 	"LowerLeg_L",	"LowerLeg.L" });
			aliasBoneName.Add(bonesName[(int)HumanBodyBones.RightLowerLeg], new string[] 	{ "RightLowerLeg" , "LowerLeg_R",	"LowerLeg.R" });
			
			aliasBoneName.Add(bonesName[(int)HumanBodyBones.LeftFoot], new string[] 		{ "LeftFoot", 		"Foot_L",		"Foot.L" });
			aliasBoneName.Add(bonesName[(int)HumanBodyBones.RightFoot], new string[] 		{ "RightFoot" , 	"Foot_R" ,		"Foot.R"});
			
			aliasBoneName.Add(bonesName[(int)HumanBodyBones.LeftToes], new string[] 		{ "LeftToes", 		"LeftToeBase", 	 "Toes_L",	"Toe.L" });
			aliasBoneName.Add(bonesName[(int)HumanBodyBones.RightToes], new string[] 		{ "RightToes", 		"RightToeBase",	 "Toes_R",	"Toe.R" });
			
			aliasBoneName.Add(bonesName[(int)HumanBodyBones.LeftShoulder], new string[] 	{ "LeftShoulder", 	"Shoulder_L",	"Shoulder.L" });
			aliasBoneName.Add(bonesName[(int)HumanBodyBones.RightShoulder], new string[] 	{ "RightShoulder", 	"Shoulder_R",	"Shoulder.R" });

			aliasBoneName.Add(bonesName[(int)HumanBodyBones.RightUpperArm], new string[] 	{ "RightUpperArm",	"UpperArm_R",	"UpperArm.R" });
			aliasBoneName.Add(bonesName[(int)HumanBodyBones.LeftUpperArm], new string[] 	{ "LeftUpperArm", 	"UpperArm_L", 	"UpperArm.L"});
			
			aliasBoneName.Add(bonesName[(int)HumanBodyBones.LeftLowerArm], new string[] 	{ "LeftLowerArm", 	"LowerArm_L", 	"LowerArm.L" });
			aliasBoneName.Add(bonesName[(int)HumanBodyBones.RightLowerArm], new string[] 	{ "RightLowerArm", 	"LowerArm_R",	"LowerArm.R" });

			aliasBoneName.Add(bonesName[(int)HumanBodyBones.LeftHand], new string[] 		{ "LeftHand", 		"Hand_L",		"Hand.L" });
			aliasBoneName.Add(bonesName[(int)HumanBodyBones.RightHand], new string[] 		{ "RightHand",		"Hand_R",		"Hand.R" });
		}
		
		protected Transform FixGetBoneTransform(Animator animator, int i)
        {
			if (i == 21)//armature
				return animator.GetBoneTransform(HumanBodyBones.Hips).parent;
			else
				return animator.GetBoneTransform((HumanBodyBones)i);
        }
		
		protected bool MatchStr(string subStr, string orginStr)
        {
			if(orginStr.Equals(subStr))
				return true;
			else if (isIgnoreCase&&subStr.Equals(orginStr, System.StringComparison.OrdinalIgnoreCase))
				return true;
			else if (isSubString&&orginStr.ToUpper().Contains(subStr.ToUpper()))
				return true;
			
			return false;
        }
	}

}
#endif